﻿using System;
using System.Collections.Generic;
using System.Text;

using EnvDTE;
using EnvDTE80;

namespace PropertyAddin
{
	class ClassManager
	{
		#region Public Constants
		public const string REGION_START = "#region";
		public const string REGION_STOP = "#endregion";
		#endregion Public Constants

		#region Private Members
		private DTE2 m_ApplicationObject;
		private List<IStruct> m_Classes;
		private IStruct m_CurrentStructure;
		private Dictionary<string, ClassField> m_Fields;
		private Dictionary<string, CodeProperty> m_Properties;
		private List<string> m_UsingList;
		#endregion Private Members

		#region Constructors
		/// <summary>
		/// Creates a class manager for that application object.
		/// </summary>
		/// <param name="applicObject"></param>
		public ClassManager(DTE2 applicObject)
		{
			m_ApplicationObject = applicObject;
			m_UsingList = null;
			this.LoadClasses();
		}

		/// <summary>
		/// Creates a class manager only for that current structure (a class or a struct).
		/// </summary>
		/// <param name="currentStructure"></param>
		public ClassManager(CodeElement currentStructure)
		{
			m_ApplicationObject = null;
			m_UsingList = null;
			m_Classes = new List<IStruct>();
			this.ProcessElement(currentStructure);

			foreach (IStruct s in m_Classes)
			{
				if (currentStructure.Name == s.Name)
				{
					this.SelectClass(s);
					return;
				}
			}
			throw new ApplicationException(string.Format("Class or struct not found: {0}",
				currentStructure.Name));
		}
		#endregion Constructors

		#region Public Attributes
		/// <summary>
		/// Gets the current application object associated with the class manager.
		/// </summary>
		public DTE2 ApplicationObject
		{
			get { return m_ApplicationObject; }
		}

		/// <summary>
		/// Gets the current class or struct structure.
		/// </summary>
		public IStruct CurrentStructure
		{
			get { return m_CurrentStructure; }
		}

		/// <summary>
		/// Gets the classes collection found in the current document of the current application.
		/// </summary>
		public List<IStruct> Classes
		{
			get { return m_Classes; }
		}

		/// <summary>
		/// Gets the fields enumeration of the current class (or struct) of the manager.
		/// </summary>
		public IEnumerable<ClassField> Fields
		{
			get { return m_Fields.Values; }
		}

		/// <summary>
		/// Gets the class field data instance of the current class (or struct) with that name.
		/// </summary>
		/// <param name="name"></param>
		/// <returns></returns>
		public ClassField this[string name]
		{
			get { return m_Fields.ContainsKey(name) ? m_Fields[name] : null; }
		}
		#endregion Public Attributes

		#region Public Methods
		/// <summary>
		/// Loads all members present in the class selected.
		/// </summary>
		/// <param name="_class"></param>
		public void SelectClass(IStruct _class)
		{
			m_CurrentStructure = _class;
			m_Fields = new Dictionary<string, ClassField>();
			m_Properties = new Dictionary<string, CodeProperty>();

			// Load all members
			foreach (CodeElement element in _class.Members)
			{
				vsCMElement kind = element.Kind;

				if (kind == vsCMElement.vsCMElementProperty)
				{
					CodeProperty property = element as CodeProperty;

					if (property.Name != "this")
						m_Properties.Add(property.Name, property);
				}
				else if (kind == vsCMElement.vsCMElementVariable)
				{
					CodeVariable variable = element as CodeVariable;
					ClassField cf = new ClassField(variable, false);

					m_Fields.Add(cf.Name, cf);
				}
			}

			foreach (CodeElement element in _class.Bases)
			{
				if (element.Kind == vsCMElement.vsCMElementClass)
				{
					CodeClass item = element as CodeClass;

					foreach (CodeElement elmnt in item.Members)
					{
						if (elmnt.Kind == vsCMElement.vsCMElementVariable)
						{
							CodeVariable variable = elmnt as CodeVariable;

							if (!m_Fields.ContainsKey(variable.Name)
								&& variable.Access == vsCMAccess.vsCMAccessProtected)
							{
								ClassField cf = new ClassField(variable, true);
								m_Fields.Add(cf.Name, cf);
							}
						}
					}
				}
			}

			foreach (string name in m_Fields.Keys)
			{
				CodeProperty property;
				ClassField cf = m_Fields[name];

				if (m_Properties.TryGetValue(cf.PropertyName, out property))
					cf.Property = property;
			}
			this.LoadUsingList();
		}

		/// <summary>
		/// Writes the property corresponding with that field in the current class at the defined position.
		/// </summary>
		/// <param name="field"></param>
		/// <param name="createGet"></param>
        /// <param name="createSet"></param>
        /// <param name="setIsInternal"></param>
        /// <param name="position"></param>
		public void WriteProperty(ClassField field, bool createGet, bool createSet, bool setIsInternal,
			object position, string getTemplate, string setTemplate)
		{
			const string REGION_NAME = " Public Attributes";
			string regionStart = string.Concat(REGION_START, REGION_NAME);
			string regionStop = string.Concat(REGION_STOP, REGION_NAME); ;
			CodeProperty property;
			EditPoint point1, point2;

			if (field.Property == null)
			{
				CodeElement lastProperty = this.FindLastProperty();

				property = m_CurrentStructure.AddProperty(field, createGet, createSet, position);
				if (createGet)
				{
					if (field.Variable.IsShared)
						property.Getter.IsShared = true;
					point1 = property.Getter.StartPoint.CreateEditPoint();
					point2 = property.Getter.EndPoint.CreateEditPoint();
					point1.Delete(point2);
					point1.Insert(field.GetMethod(getTemplate));
				}
				if (createSet)
				{
					if (field.Variable.IsShared)
						property.Setter.IsShared = true;
					point1 = property.Setter.StartPoint.CreateEditPoint();
					point2 = property.Setter.EndPoint.CreateEditPoint();
					point1.Delete(point2);
					point1.Insert(field.SetMethod(setIsInternal, setTemplate));
				}
				InsertComment(property.StartPoint, GetPropertyComment(field, createGet, createSet, false,
					false));

				if (lastProperty == null)
				{
					string indentation = GetIndentation(property.StartPoint);

					point1 = GetCommentStartPoint(property.StartPoint);
					point1.Insert(indentation);
					point1.Insert(regionStart);
					point1.Insert(Environment.NewLine);

					point1 = property.EndPoint.CreateEditPoint();
					point1.Insert(Environment.NewLine);
					point1.Insert(indentation);
					point1.Insert(regionStop);
				}
				else
				{
					CodeElement p = GetPropertyElement(property.Name);

					MoveEndRegion(p, REGION_NAME);
					point1 = Find(property.EndPoint, regionStart, false);
					if (point1 != null)
					{
						string line = CutLine(point1);
						point1 = GetCommentStartPoint(property.StartPoint);
						point1.Insert(line);
					}
				}
			}
			else
			{
				property = field.Property;
				if (createGet)
				{
					point1 = property.Setter.StartPoint.CreateEditPoint();
					point1.Insert(field.GetMethod(getTemplate));
					point1.Insert(Environment.NewLine);
				}
				if (createSet)
				{
					point1 = property.Getter.EndPoint.CreateEditPoint();
					point1.Insert(Environment.NewLine);
					point1.Insert(field.SetMethod(setIsInternal, setTemplate));
				}

				// Reformat and indent.
				point1 = property.StartPoint.CreateEditPoint();
				point1.SmartFormat(property.EndPoint);
				UpdateComment(property, GetPropertyComment(field, createGet, createSet, false, false));
			}
		}

		/// <summary>
		/// Deletes the property getter or setter corresponding with that field in the current class.
		/// </summary>
		/// <param name="field"></param>
		/// <param name="deleteGet"></param>
		/// <param name="deleteSet"></param>
		public void RemoveProperty(ClassField field, bool deleteGet, bool deleteSet)
		{
			if (field.Property != null)
			{
				CodeProperty property = field.Property;
				EditPoint point1 = null, point2;

				if ((deleteGet || property.Getter == null) && (deleteSet || property.Setter == null))
				{
					point1 = GetCommentStartPoint(property.StartPoint);
					point2 = property.EndPoint.CreateEditPoint();
					point1.Delete(point2);
				}
				else if (deleteGet || deleteSet)
				{
					if (deleteGet)
						point1 = property.Getter.StartPoint.CreateEditPoint();
					if (deleteSet)
						point1 = property.Setter.StartPoint.CreateEditPoint();

					point1.StartOfLine();
					point2 = point1.CreateEditPoint();
					point2.LineDown(1);
					point1.Delete(point2);
					UpdateComment(property, GetPropertyComment(field, false, false, deleteGet, deleteSet));
				}
			}
		}

		/// <summary>
		/// Indicates if a constructor with these parameters exists in the current class.
		/// </summary>
		/// <param name="parameters"></param>
		/// <returns></returns>
		public bool IsConstructorExisting(List<ConstructorParameter> parameters)
		{
			foreach (CodeElement element in this.GetConstructorsList().Keys)
			{
				CodeFunction constructor = element as CodeFunction;

				if (parameters.Count == constructor.Parameters.Count)
				{
					int i = 0;
					bool result = true;

					foreach (CodeElement p in constructor.Parameters)
					{
						CodeParameter parameter = p as CodeParameter;

						if (parameter.Type.AsString != parameters[i].Type.AsString)
						{
							result = false;
							break;
						}
						++i;
					}
					if (result)
						return true;
				}
			}
			return false;
		}

		/// <summary>
		/// Writes the constructor with these parameters in the current class at the defined position.
		/// </summary>
		/// <param name="parameters"></param>
		/// <param name="baseParameterCount"></param>
		/// <param name="position"></param>
		public void WriteConstructor(List<ConstructorParameter> parameters, int baseParameterCount,
			object position)
		{
			CodeFunction constructor = m_CurrentStructure.AddConstructor(position);
			string commentText = "Creates an instance without any parameter.";
			EditPoint point;
			string indentations;

			// Add the parameters.
			foreach (ConstructorParameter cp in parameters)
				constructor.AddParameter(cp.Name, this.GetShortTypeName(cp.Type), -1);

			// Insert the base clause.
			if (baseParameterCount > 0)
			{
				point = Find(constructor.StartPoint, "{", false);
				point.CharLeft(1);
				point.Insert(": base(");

				for (int i = 0; i < baseParameterCount; ++i)
				{
					if (i > 0)
						point.Insert(", ");
					point.Insert(parameters[i].Name);
				}
				point.Insert(")");
			}

			// Add the implementation lines.
			point = Find(constructor.StartPoint, "{", false);
			point.CharRight(1);
			foreach (ConstructorParameter cp in parameters)
				if (!string.IsNullOrEmpty(cp.FieldName))
					point.Insert(string.Format("{0} = {1};{2}", cp.FieldName, cp.Name, Environment.NewLine));

			// Reformat and indent.
			point = constructor.StartPoint.CreateEditPoint();
			point.SmartFormat(constructor.EndPoint);
			if (parameters.Count > 0)
				commentText = "Creates an instance with these parameters.";
			point = InsertComment(constructor.StartPoint, commentText);
			indentations = GetIndentation(point);
			foreach (ConstructorParameter cp in parameters)
			{
				point.Insert("/// <param name=\"");
				point.Insert(cp.Name);
				point.Insert("\"></param>");
				point.Insert(Environment.NewLine);
				point.Insert(indentations);
			}
		}

		/// <summary>
		/// Gets the list of each field of the current class.
		/// </summary>
		/// <returns></returns>
		public Dictionary<CodeElement, int> GetFieldsList()
		{
			Dictionary<CodeElement, int> list = new Dictionary<CodeElement, int>();
			int index = 0;

			foreach (CodeElement element in m_CurrentStructure.Members)
			{
				if (element.Kind == vsCMElement.vsCMElementVariable)
					list.Add(element, index);
				++index;
			}
			return list;
		}

		/// <summary>
		/// Finds the first field in the current class.
		/// </summary>
		/// <returns></returns>
		public CodeElement FindFirstField()
		{
			foreach (CodeElement element in m_CurrentStructure.Members)
				if (element.Kind == vsCMElement.vsCMElementVariable)
					return element;
			return null;
		}

		/// <summary>
		/// Finds the last field in the current class.
		/// </summary>
		/// <returns></returns>
		public CodeElement FindLastField()
		{
			CodeElement element = null;

			foreach (CodeElement element2 in m_CurrentStructure.Members)
				if (element2.Kind == vsCMElement.vsCMElementVariable)
					element = element2;
			return element;
		}

		/// <summary>
		/// Finds the last property in the current class.
		/// </summary>
		/// <returns></returns>
		public CodeElement FindLastProperty()
		{
			CodeElement element = null;

			foreach (CodeElement element2 in m_CurrentStructure.Members)
				if (element2.Kind == vsCMElement.vsCMElementProperty)
					element = element2;
			return element;
		}

		/// <summary>
		/// Gets the code element of the property with that name.
		/// </summary>
		/// <param name="name"></param>
		/// <returns></returns>
		public CodeElement GetPropertyElement(string name)
		{
			CodeElement element = null;

			foreach (CodeElement element2 in m_CurrentStructure.Members)
				if (element2.Name == name && element2.Kind == vsCMElement.vsCMElementProperty)
					element = element2;
			return element;
		}

		/// <summary>
		/// Finds the previous property of that target field (to insert its property after the found one).
		/// </summary>
		/// <returns></returns>
		public CodeElement FindPreviousProperty(ClassField targetField)
		{
			List<string> fieldNames = new List<string>(m_Fields.Keys);
			int index = fieldNames.IndexOf(targetField.Name);
			string name;

			while (index > 0)
			{
				ClassField field = null;

				--index;
				name = fieldNames[index];
				field = m_Fields[name];
				if (field.Property != null)
					return this.GetPropertyElement(field.PropertyName);
			}
			return null;
		}

		/// <summary>
		/// Gets the list of each constructor of the current class.
		/// </summary>
		/// <returns></returns>
		public Dictionary<CodeElement, int> GetConstructorsList()
		{
			Dictionary<CodeElement, int> list = new Dictionary<CodeElement, int>();
			int index = 0;

			foreach (CodeElement element in m_CurrentStructure.Members)
			{
				if (IsConstructor(m_CurrentStructure, element))
					list.Add(element, index);
				++index;
			}
			return list;
		}

		/// <summary>
		/// Finds the first constructor in the current class.
		/// </summary>
		/// <returns></returns>
		public CodeElement FindFirstConstructor()
		{
			foreach (CodeElement element in m_CurrentStructure.Members)
				if (IsConstructor(m_CurrentStructure, element))
					return element;
			return null;
		}

		/// <summary>
		/// Finds the last constructor in the current class.
		/// </summary>
		/// <returns></returns>
		public CodeElement FindLastConstructor()
		{
			CodeElement result = null;

			foreach (CodeElement element in m_CurrentStructure.Members)
				if (IsConstructor(m_CurrentStructure, element))					
					result = element;
			return result;
		}

		/// <summary>
		/// Gets the shortest string representation of that type.
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		public string GetShortTypeName(CodeTypeRef type)
		{
			StringBuilder sb = new StringBuilder(type.AsString);

			foreach (string u in m_UsingList)
				sb.Replace(string.Concat(u, '.'), string.Empty);
			for (int i = 0; i < sb.Length - 1; ++i)
				if (sb[i] == ',' && sb[i + 1] != ' ')
					sb.Insert(i + 1, ' ');
			return sb.ToString();
		}
		#endregion Public Methods

		#region Public Static Methods
		/// <summary>
		/// Gets the indentation string between that edit point and the start of the same line.
		/// </summary>
		/// <param name="startPoint"></param>
		/// <returns></returns>
		public static string GetIndentation(TextPoint startPoint)
		{
			EditPoint point = startPoint.CreateEditPoint();

			point.StartOfLine();
			return point.GetText(startPoint);
		}

		/// <summary>
		/// Gets the edit point start of the comment XML lines before that starting point.
		/// </summary>
		/// <param name="startPoint"></param>
		/// <returns></returns>
		public static EditPoint GetCommentStartPoint(TextPoint startPoint)
		{
			EditPoint point1 = startPoint.CreateEditPoint();

			while (true)
			{
				EditPoint point2 = point1.CreateEditPoint();
				EditPoint point3;
				string line;

				point2.LineUp(1);
				point2.StartOfLine();
				point3 = point2.CreateEditPoint();
				point3.EndOfLine();
				line = point2.GetText(point3).TrimStart(' ', '\t');
				if (line.StartsWith("///"))
					point1 = point2;
				else
					break;
			}
			return point1;
		}

		/// <summary>
		/// Finds that pattern starting the search from that start point. When the last parameter is true,
		/// the search is backwards.
		/// </summary>
		/// <param name="startPoint"></param>
        /// <param name="pattern"></param>
        /// <param name="back"></param>
        /// <returns></returns>
		public static EditPoint Find(TextPoint startPoint, string pattern, bool back)
		{
			EditPoint point1 = startPoint.CreateEditPoint();
			vsFindOptions option = back ? vsFindOptions.vsFindOptionsBackwards
				: vsFindOptions.vsFindOptionsNone;
			EditPoint point2 = null;
			TextRanges tags = null;

			if (point1.FindPattern(pattern, (int)option, ref point2, ref tags))
				return point2;
			else
				return null;
		}

		/// <summary>
		/// Moves the markup of region's end with that name found before that code element to a position
		/// after that code element.
		/// </summary>
		/// <param name="element"></param>
		/// <param name="regionName"></param>
		public static void MoveEndRegion(CodeElement element, string regionName)
		{
			string regionStop = string.Concat(REGION_STOP, regionName);
			EditPoint point1 = GetCommentStartPoint(element.StartPoint);
			EditPoint point2 = Find(point1, regionStop, true);

			if (point2 != null)
			{
				string line = CutLine(point2);

				point1 = element.EndPoint.CreateEditPoint();
				point1.StartOfLine();
				point1.LineDown(1);
				point1.Insert(line);
			}
		}
		#endregion Public Static Methods

		#region Private Methods
		/// <summary>
		/// Loads all class present in the active documents.
		/// </summary>
		private bool LoadClasses()
		{
			m_Classes = new List<IStruct>();
			if (m_ApplicationObject.ActiveDocument == null
				|| m_ApplicationObject.ActiveDocument.ProjectItem == null
				|| m_ApplicationObject.ActiveDocument.ProjectItem.FileCodeModel == null
				|| m_ApplicationObject.ActiveDocument.ProjectItem.FileCodeModel.CodeElements == null)
			{
				return false;
			}
			else
				foreach (CodeElement element in m_ApplicationObject.ActiveDocument.ProjectItem.FileCodeModel.CodeElements)
					this.ProcessElement(element);

			return true;
		}

		/// <summary>
		/// Retrieves all 'class' and 'struct' in the active document.
		/// </summary>
		/// <param name="elem"></param>
		private void ProcessElement(CodeElement elem)
		{
			if (elem.Kind == vsCMElement.vsCMElementNamespace)
			{
				CodeNamespace ns = elem as CodeNamespace;
				foreach (CodeElement element in ns.Members)
					this.ProcessElement(element);
			}
			else if (elem.Kind == vsCMElement.vsCMElementClass)
			{
				ClassStructure item = new ClassStructure(elem as CodeClass);

				m_Classes.Add(item);
				foreach (CodeElement element in item.Members)
					this.ProcessElement(element);
			}
			else if (elem.Kind == vsCMElement.vsCMElementStruct)
			{
				StructStructure item = new StructStructure(elem as CodeStruct);
				m_Classes.Add(item);
			}
		}

		/// <summary>
		/// Extracts all existing using clauses from the active document.
		/// </summary>
		private void LoadUsingList()
		{
			EditPoint point1 = m_CurrentStructure.StartPoint.CreateEditPoint(), endOfDocument;

			m_UsingList = new List<string>();
			point1.StartOfDocument();
			endOfDocument = point1.CreateEditPoint();
			endOfDocument.EndOfDocument();
			while (point1.LessThan(endOfDocument))
			{
				EditPoint point2 = point1.CreateEditPoint();
				int length = -1, pos;
				string usingLine;

				point2.EndOfLine();
				usingLine = point1.GetText(point2).TrimStart(' ', '\t');
				if (usingLine.StartsWith("using"))
					length = 5;
				else if (usingLine.StartsWith("namespace"))
					length = 9;
				if (length != -1)
				{
					pos = IndexOfAny(usingLine.IndexOf("//", length), usingLine.IndexOf(';', length),
						usingLine.IndexOf('(', length));

					if (pos == -1 || usingLine[pos] != '(')
					{
						if (pos == -1)
							pos = usingLine.Length;
						usingLine = usingLine.Substring(length, pos - length);
						usingLine = usingLine.Replace(" ", "").Replace("\t", "");
						m_UsingList.Add(usingLine);
					}
				}
				point1.LineDown(1);
			}
			m_UsingList.Sort();
			m_UsingList.Reverse();
		}
		#endregion Private Methods

		#region Private Static Methods
		/// <summary>
		/// Indicates if that code element is a constructor of that structure.
		/// </summary>
		/// <param name="structure"></param>
		/// <param name="element"></param>
		/// <returns></returns>
		private static bool IsConstructor(IStruct structure, CodeElement element)
		{
			return element.Kind == vsCMElement.vsCMElementFunction
				&& element.Name == structure.Name;
		}

		/// <summary>
		/// Builds the property comment content for that field and corresponding to its new state (defined
		/// by these boolean parameters).
		/// </summary>
		/// <param name="field"></param>
		/// <param name="createGet"></param>
		/// <param name="createSet"></param>
		/// <param name="deleteGet"></param>
		/// <param name="deleteSet"></param>
		/// <returns></returns>
		private static string GetPropertyComment(ClassField field, bool createGet, bool createSet,
			bool deleteGet, bool deleteSet)
		{
			bool hasGetter = (createGet || field.HasGetter) && !deleteGet;
			bool hasSetter = (createSet || field.HasSetter) && !deleteSet;
			EditPoint point1 = field.Property.StartPoint.CreateEditPoint();
			EditPoint point2 = field.Property.EndPoint.CreateEditPoint();
			bool setIsInternal = point1.GetText(point2).IndexOf("internal set") != -1;
			StringBuilder sb = new StringBuilder();

			if (hasGetter)
				sb.Append("Gets");
			if (hasSetter)
			{
				if (hasGetter)
				{
					sb.Append(' ');
					if (setIsInternal)
						sb.Append('(');
					sb.Append("or sets");
					if (setIsInternal)
						sb.Append(' ');
				}
				else
				{
					sb.Append("Sets");
					if (setIsInternal)
						sb.Append(" (");
				}
				if (setIsInternal)
					sb.Append("internally)");
			}
			sb.AppendFormat(" the {0}.", field.PropertyName.ToLower());
			return sb.ToString();
		}

		/// <summary>
		/// Builds the comment XML lines for the code element which starts at that text point with that
		/// content before the code of code element. Returns the edit point after the comment summary.
		/// </summary>
		/// <param name="startPoint"></param>
		/// <param name="comment"></param>
		/// <returns></returns>
		private static EditPoint InsertComment(TextPoint startPoint, string comment)
		{
			EditPoint point = startPoint.CreateEditPoint();
			string tabulations = GetIndentation(point);

			point.Insert("/// <summary>");
			point.Insert(Environment.NewLine);

			point.Insert(tabulations);
			point.Insert("/// ");
			point.Insert(comment);
			point.Insert(Environment.NewLine);

			point.Insert(tabulations);
			point.Insert("/// </summary>");
			point.Insert(Environment.NewLine);
			point.Insert(tabulations);
			return point;
		}

		/// <summary>
		/// Gets the index of that pattern's end in that text starting the search at that index.
		/// </summary>
		/// <param name="text"></param>
		/// <param name="pattern"></param>
		/// <param name="startIndex"></param>
		/// <returns></returns>
		private static int IndexOfPatternEnd(string text, string pattern, int startIndex)
		{
			int index = text.IndexOf(pattern, startIndex);
			return (index == -1 ? -1 : index + pattern.Length);
		}

		/// <summary>
		/// Gets the index of the header's end in that comment.
		/// </summary>
		/// <param name="comment"></param>
		/// <returns></returns>
		private static int IndexOfHeaderEnd(string comment)
		{
			string lowerText = comment.ToLower();
			int getsIndex = IndexOfPatternEnd(lowerText, "gets", 0);
			int setsIndex = IndexOfPatternEnd(lowerText, "sets", 0);

			if (setsIndex == -1)
				return getsIndex;
			else
			{
				int index = 0;
				string sep = "#";

				if (getsIndex != -1)
					index = getsIndex;
				index = lowerText.IndexOf('(', index);
				if (index > setsIndex)
					sep = lowerText.Substring(setsIndex, index - setsIndex);
				if (index != -1 && (index < setsIndex || string.IsNullOrEmpty(sep.Trim(' ', '\t'))))
				{
					index = IndexOfPatternEnd(lowerText, ")", setsIndex);
					if (index != -1)
						return index;
				}
				return setsIndex;
			}
		}

		/// <summary>
		/// Updates the comment XML lines of that property with that default content before the code of
		/// the property.
		/// </summary>
		/// <param name="property"></param>
		/// <param name="defaultComment"></param>
		private static void UpdateComment(CodeProperty property, string defaultComment)
		{
			EditPoint point1 = property.StartPoint.CreateEditPoint();
			EditPoint point2 = GetCommentStartPoint(point1);

			if (point2.EqualTo(point1))
				InsertComment(property.StartPoint, defaultComment);
			else
			{
				point1 = Find(point2, "/// <summary>", false);
				if (point1 != null)
				{
					int indexOfDefaultHeader = IndexOfHeaderEnd(defaultComment);
					string currentComment;
					int indexOfCurrentHeader;

					point1.LineDown(1);
					point1.StartOfLine();
					point2 = Find(point1, "/// ", false);
					point1 = point2.CreateEditPoint();
					point1.EndOfLine();
					currentComment = point2.GetText(point1);

					point2.Insert(defaultComment.Substring(0, indexOfDefaultHeader));
					indexOfCurrentHeader = IndexOfHeaderEnd(currentComment);
					if (indexOfCurrentHeader != -1)
						point2.Delete(indexOfCurrentHeader);
				}
			}
		}

		/// <summary>
		/// Cuts the whole line containing that text point from the code and returns the cut string.
		/// </summary>
		/// <param name="startPoint"></param>
		/// <returns></returns>
		private static string CutLine(TextPoint startPoint)
		{
			EditPoint point1 = startPoint.CreateEditPoint();
			EditPoint point2;
			string line;

			point1.StartOfLine();
			point2 = point1.CreateEditPoint();
			point2.LineDown(1);
			line = point1.GetText(point2);
			point1.Delete(point2);
			return line;
		}

		/// <summary>
		/// Gets the lower index different of -1 in that parameters list.
		/// </summary>
		/// <param name="indexes"></param>
		/// <returns></returns>
		private static int IndexOfAny(params int[] indexes)
		{
			int index = -1;

			foreach (int i in indexes)
				if (index == -1 || (i > -1 && i < index))
					index = i;
			return index;
		}
		#endregion Private Static Methods
	}
}
